
let module = 'inject_script'

/**
 * Сервисный класс с общими методами
 * Для импорта использовать: import Service from './service_0_0_4_3.js'
 */
class Service {

	/////////////////////////////////
	/**
	 * Получить переменные объекта в виде Json
	 * @param {Object} inspectObject объект, содержимое которого будет выведено
	 * @param {*} onlyLocal true только локальные переменные, false локальные и глобальные
	 * @returns
	 */
	static getLocalAndGlobalVariables(inspectObject, onlyLocal) {
		const iframe = window.document.createElement("iframe");
		iframe.src = "about:blank";
		let browserGlobals = {}

		if (document.head) {
			window.document.head.appendChild(iframe);
			browserGlobals = Object.keys(iframe.contentWindow);
			window.document.head.removeChild(iframe);
		} else if (document.documentElement) {
			window.document.documentElement.appendChild(iframe);
			browserGlobals = Object.keys(iframe.contentWindow);
			window.document.documentElement.removeChild(iframe);
		}
		let runtimeGlobals = {}
		Object.keys(inspectObject).forEach(key => {
			if (browserGlobals.includes(key) !== onlyLocal) {
				let typeValue;
				if (Service.isObject(inspectObject[key])) {
					typeValue = 'object'
					// TODO Подумать о рекурсивном обходе текущего объекта
					if (Object.getOwnPropertyNames(inspectObject[key]).length !== 0) {
						// console.log(key, Object.getOwnPropertyNames(inspectObject[key]))
					}
				} else if (Array.isArray(inspectObject[key])) {
					typeValue = 'array'
				} else if (inspectObject[key] === null) {
					typeValue = null
				} else if (inspectObject[key] === undefined) {
					typeValue = "undefined"
				} else {
					typeValue = typeof inspectObject[key]
				}
				runtimeGlobals[key] = { name: key, type: typeValue }
			}
		})

		// let result = {}
		// result[name] = runtimeGlobals
		return runtimeGlobals
	}
	// console.log(JSON.stringify(getLocalAndGlobalVariables(
	//			chrome.devtools.panels, 'Globals Variables for Content Script', true), null, 4))

	/**
	 * Получить содержимое объекта
	 * @param {Object} inspectObject изучаемый объект
	 * @param {boolean} isNullShow показывать или нет значения null
	 * @returns содержимое объекта
	 * @deprecated имеет малую область видимости вложенности объекта
	 */
	static getObjectScoupe(inspectObject, isNullShow) {
		let values = {}
		Object.keys(inspectObject).forEach(key => {
			if (typeof inspectObject[key] === 'function') {
				values[key] = 'function'
			} else if (Service.isObject(inspectObject[key])) {
				let value = convertToJson(inspectObject[key])
				values[key] = value
			} else {
				let value = convertToJson(inspectObject[key])
				if ((value === null) === isNullShow) {
					values[key] = value
				}
			}
		});
		let result = {}
		result[inspectObject.constructor.name] = values
		return result
	}
	// console.log(JSON.stringify(getObjectScoupe(chrome.devtools.panels, false), null, 4))

	/**
	 * Создать HTML объект
	 * @param {string} type тип объекта (div, li, p и т.д.)
	 * @param {string} textContent текст объекта
	 * @param {Array[]} attributes (не обязательно) атрибуты элемента
	 * @returns HTML элемент
	 */
	static createCustomElement(type, textContent, attributes) {
		var result = document.createElement(type);
		result.textContent = textContent;

		if (attributes && attributes.length > 0) {
			for (var key in attributes) {
				var attribute = attributes[key];
				var name = attribute['name'];
				var value = attribute['value'];
				result.setAttribute(name, value);
			}
		}
		return result;
	}

	/**
	 * Рекурсивный обход содержимого объекта и вложенных объектов с формированием
	 * @param {object} inspectObject объект, содержимое которого будет просматриваться
	 * @param {*} resultData (не указывается) объект для возврата значения из рекурсии
	 * @returns объект, содержащий ключи и значений исследуемого объекта
	 */
	static getObjectAllScoupe(inspectObject, resultData) {
		if (resultData == undefined) {
			console.log('resultData = undefined')
			resultData = new Object()
		}
		if (inspectObject != null) {
			var keys = Object.getOwnPropertyNames(inspectObject)

			var length = keys.length;
			if (length !== 0) {
				for (let i = 0; i < length; i++) {
					let key = keys[i]
					let val = inspectObject[key]
					try {
						resultData[key] = ''

						if (Service.isObject(val)) {
							resultData[key] = convertToJson(val)
							this.getObjectAllScoupe(val, resultData[key])
						} else {
							resultData[key] = typeof val
						}
					} catch (e) {
						console.log('Значение с ключом не добавлено в объект: ' + key)
					}
				}
			}
		}
		return resultData;
	}

	/**
	 * Рекурсивный обход содержимого объекта с составлением путей ко всем элементам объекта
	 * @param {Object} inspectObject объект, содержимое которого будет просматриваться
	 * @param {*} level (не обязательно) корневой уровень пути объекта, при необходимости указать равным имени базового объекта
	 * @param {*} keysResult (не указывается) объект для возврата значения из рекурсии
	 * @returns
	 */
	static getObjectKeys(inspectObject, level, keysResult) {
		if (keysResult == undefined) {
			keysResult = new Array()
		}
		function getLevel(val1, val2) {
			if (val1 === undefined) {
				return val2;
			} else {
				return val1 + '.' + val2;
			}
		}
		if (inspectObject != null) {
			var keys = Object.keys(inspectObject); //getOwnPropertyNames
			var length = keys.length;
			if (length !== 0) {
				for (var i = 0; i < length; i++) {
					let key = keys[i]
					let val = inspectObject[key]
					if (Array.isArray(val)) {
						this.getObjectKeys(val, getLevel(level, key), keysResult);
					} else if (Service.isObject(val)) {
						// Для построения путей для GPath Groovy использовать на проверку на число, будут сгенерированы
						// пути с номером элемента в массиве
						if (/\d/.test(key) && (typeof key === 'number' && isFinite(key))) {
							this.getObjectKeys(val, level + '[' + key + ']', keysResult);
						} else {
							// Для построения путей для JS использовать только данный блок, будут сгенерированы пути через '.'
							this.getObjectKeys(val, getLevel(level, key), keysResult);
						}
					} else {
						// Сохранить только пути
						// keysResult.push(getLevel(level, key));

						// Сохранить путь, тип данных значения и значение
						let node = new Object()
						node['path'] = getLevel(level, key)
						node['type'] = Service.getType(val)
						node['value'] = val
						keysResult.push(node);
					}
				}
			}
		}
		return keysResult;
	}
	// console.log('getObjectKeys', JSON.stringify(Service.getObjectKeys(chrome), null, 4))

	/**
	 * Получить значение из объекта по указанному пути
	 * @param {Object} object объект, из которого будет получено значение
	 * @param {string} path путь к значению
	 * @returns
	 */
	static getValueToPath(object, path) {
		var obj = object;
		var path = path.split('.');
		for (var i = 0; i < path.length; i++) {
			obj = obj[path[i]];
		}
		return obj;
	}

	/**
	 * В списке путей к объекту или Json найти общий путь
	 * @param {Array} keysForFind список путей в объекте для поиска
	 * @param {*} testWord (не указывается) параметр для рекурсивного обхода элемента списка
	 * @returns общий для всех элементов путь
	 */
	static getBasePath(keysForFind, testWord) {
		console.log('> Работа со словом: ' + testWord)
		if (testWord === undefined) {
			testWord = ''
		}
		let length = keysForFind.length
		for (let i = 0; i < length; i++) {
			let splitKeys = keysForFind[i].split('.')
			// let newSplitKeys = splitKeys
			let newSplitKeys = new Array()
			for (let i = 0; i < splitKeys.length; i++) {
				if (i !== splitKeys.length - 1) {
					newSplitKeys.push(splitKeys[i])
					newSplitKeys.push('.')
				} else {
					newSplitKeys.push(splitKeys[i])
				}
			}
			console.log('> Разбитый массив с точками', newSplitKeys)
			if (newSplitKeys.length > 1) {
				for (let elem of newSplitKeys) {
					let str = elem// + '.'
					testWord += str
					console.log('> Фильтр по: ' + testWord)
					let filterKeysForFind = keysForFind.filter(function (o) {
						if (o.includes(testWord)) {
							return true
						}
					})
					console.log('> Отфильтровано элементов: ' + filterKeysForFind.length, filterKeysForFind)
					if (filterKeysForFind.length === 0) {
						return Service.getBasePath(filterKeysForFind, testWord.substring(0, testWord.length - str.length))
					}
					// if (filterKeysForFind.length === 1) {
					// 	return testWord
					// }
				}
			}
			// else {
			// console.log('> 31', newSplitKeys)
			// let filterKeysForFind = keysForFind.filter(function (o) {
			// 	if (o.includes(testWord)) {
			// 		return true
			// 	}
			// })
			// console.log('> 32', filterKeysForFind)
			// if (filterKeysForFind.length === 1) {
			// 	return Service.getBasePath(filterKeysForFind, testWord)
			// }
			// }
		}
		return testWord
	}

	/**
	 * TODO Дополнить описание
	 * @param {*} paths
	 * @param {*} result
	 * @returns
	 */
	static groupPaths(paths, result) {
		if (result === undefined) {
			result = new Array()
		}

		let basePath = Service.getBasePath(paths)
		console.log('basePath', basePath)

		let f = paths.filter(function (elem) {
			if (elem.includes(basePath)) {
				return true
			}
		})

		result.push(
			f.map(function (elem) {
				return elem.replace(basePath, '')
			})
		)

		let d = paths.filter(function (elem) {
			if (!elem.includes(basePath)) {
				return true
			}
		})

		if (d.length !== 0) {
			Service.groupPaths(d, result)
		}

		return result
	}

	/**
 	* Проверить содержимое переменной на соответствие типу данных Object
	* TODO Актуализировать работу класса с данным методом в связи с переносом
 	* @param {*} value проверяемая переменная
 	* @returns true - объект, false - не объект
 	*/
	static isObject(value) {
		return (typeof value === 'object' && value !== null && !Array.isArray(value))
	}

	/**
	 * Получение типа данных переменной
	 * @param {*} obj переменная, тип данных содержимого которой необходимо получить
	 * @returns
	 */
	static getType(obj) {
		if (Service.isObject(obj)) {
			return 'object'
		} else if(Array.isArray(obj)) {
			return 'array' // Не декларированный тип в языке
		}
		return typeof obj
	}

	/**
	 * Прочитать текстовый файл по указанному пути
	 * TODO Проверить работу в обычных GET запросах
	 * @param {string} file имя файла в файловой системе
	 * @returns текст из файла
	 */
	static readTextFile(file) {
		let opener = new XMLHttpRequest()
		let result = ''

		opener.open('GET', file, false)
		opener.onreadystatechange = function() {
			if (opener.readyState === 4) {
				if (opener.status === 200) {
					result = opener.responseText
				}
			}
		}

		opener.send(null)
		return result
	}
//	let path = 'chrome-extension://' + id + 'filename'
//	let dataFromFile = Service.readTextFile(path)

    /**
    * Отобразить системное диалоговое окно для сохранения текстового файла и сохранить файл
    * Метод вызывается в асинхронных методах-слушателях действий пользователя:
    * await Service.saveToFileWithDialog('123123123', options)
    * @param text {string} текст для сохранения в файле
    * @param options переменная со следующим значением: let options = { types: [ { descriptions: "json and txt files", accept: { "text/plain": [ ".json", ".txt" ] } } ] }
    */
    static async saveToFileWithDialog(text, options) {
        try {
            const handle = await window.showSaveFilePicker(options)
            const writable = await handle.createWritable()
            await writable.write(text, null, 2)
            await writable.close()
        } catch(e) {
            console.error(e)
        }
    }

    /**
    * Отобразить системное диалоговое окно для открытия текстового файла и открыть файл
    * Метод вызывается в асинхронных методах-слушателях действий пользователя:
    * let text = await Service.openFromFileWithUserDialog(options)
    * @param text {string} текст для сохранения в файле
    * @param options переменная со следующим значением: let options = { types: [ { descriptions: "json and txt files", accept: { "text/plain": [ ".json", ".txt" ] } } ] }
    */
    static async openFromFileWithUserDialog(options) {
        let content = ''
        try {
            let fileHandle
            [fileHandle] = await window.showOpenFilePicker(options)
            let file = await fileHandle.getFile()
            content = await file.text()
        } catch(e) {
            console.error(e)
        }

        return content
    }

    /**
    * Загрузить файл в браузере пользователя
    * @param filename {string} имя загружаемого файла
    * @param text {string} данные для сохранения
    */
    static download(filename, text) {
        let elem = document.createElement('a')
        elem.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))
        elem.setAttribute('download', filename)
        elem.style.display = 'none'
        document.body.appendChild(elem)

        elem.click()

        document.body.removeChild(elem)
    }

    static jsonStringify(obj) {
        return JSON.stringify(obj, null, 4)
    }

    static test(module) {
        console.log('Успешное подключение модуля: ' + module)
    }
}